package at.ac.tuwien.swa.sparrow.desktop;

import ac.at.tuwien.infosys.swa.audio.Fingerprint;
import at.ac.tuwien.swa.sparrow.common.*;
import at.ac.tuwien.swa.sparrow.server.ClientService;
import org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean;

import javax.swing.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.util.Observable;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SWAzamRuntime extends Observable {
	private static final String CONFIG_FILE = "user.properties";

	private static SWAzamRuntime runtime;

	private String baseLocation;

	final private Properties props = new Properties();

	private File baseDirectory;

	private ExecutorService backgroundExecutor = Executors.newSingleThreadExecutor();

	private ClientService clientService;

	private AudioFingerprintFunction fingerprintFunction = new AudioFingerprintFunction();

	private SWAzamRuntime(String applicationID) {
		if (baseLocation == null) {
			baseLocation = System.getProperty("user.home") + System.getProperty("file.separator") + applicationID;
		}
		baseDirectory = new File((baseLocation));
		if (!baseDirectory.isDirectory() || !baseDirectory.exists()) {
			if (!baseDirectory.mkdir()) {
				throw new RuntimeException("Unable to create installation directory");
			}
			//create it
			props.put("client.version", "0.1");
			props.put("server.url", "http://localhost:8080/remoting/client");
			props.put("myswazam.url", "http://localhost:8080/");
		} else {
			try {
				props.load(new FileInputStream(new File(baseDirectory, CONFIG_FILE)));
			} catch (IOException e) {
				throw new RuntimeException("Unable to read user configuration", e);
			}
		}
		if (!isValidConfiguration()) {
			//uups
			throw new RuntimeException("Configuration file is not valid - try to delete " + baseDirectory.getAbsolutePath() + " and start swazam again");
		}
		clientService = createServerStub();
	}

	public static synchronized void init(String applicationID) {
		if (runtime != null)
			return;
		runtime = new SWAzamRuntime(applicationID);
		Runtime.getRuntime().addShutdownHook(new Thread() {
			@Override
			public void run() {
				try {
					SWAzamRuntime.getRuntime().props.store(
							new FileOutputStream(new File(SWAzamRuntime.getRuntime().baseDirectory, CONFIG_FILE)),
							"Configuration written by SWAzam version " + SWAzamRuntime.getRuntime().props.get("client.version"));
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		});
	}

	public static SWAzamRuntime getRuntime() {
		if (runtime == null)
			throw new IllegalStateException("Runtime not initialized");
		return runtime;
	}

	public void setUser(String username, String password) {
		props.put("user", username);
		props.put("password", password);
	}

	public boolean isRegistered() {
		return props.containsKey("user") && props.containsKey("password");
	}

	public void identify(File file) {
		backgroundExecutor.submit(new IdentifyTask(file));
	}

	public URI getMySWAzamURI() {
		return URI.create(props.getProperty("myswazam.url"));
	}

	private boolean isValidConfiguration() {
		return props.containsKey("client.version") && props.containsKey("server.url") && props.containsKey("myswazam.url");
	}

	private class IdentifyTask implements Runnable {
		private File file;

		private IdentifyTask(File file) {
			this.file = file;
		}

		@Override
		public void run() {
			SWAzamRuntime.getRuntime().setChanged();
			SWAzamRuntime.getRuntime().notifyObservers(file);
			try {
				final AuthToken authToken = clientService.login(props.getProperty("user"), props.getProperty("password"));
				notify(new Random().nextInt(10));
				final Fingerprint fingerprint = fingerprintFunction.apply(file);
				notify(new Random().nextInt(30) + 10);
				final Ticket ticket = clientService.request(authToken, fingerprint);
				notify(new Random().nextInt(50) + 30);
				for (int retry = 0; retry < 3 && !Thread.currentThread().isInterrupted();) {
					try {
						final TrackResult result = clientService.result(ticket.id());
						if (result.state() != InProgress$.MODULE$) {
							notify(result);
							break;
						}
					} catch (IllegalArgumentException e) {
						retry++;
					}
					Thread.sleep(1000);
				}
				notify(null);
			} catch (NotEnoughCreditsException e) {
				JOptionPane.showMessageDialog(null, "You do not have enough credits.");
			} catch (Exception e) {
				JOptionPane.showMessageDialog(null, SWAzam.ERROR_MESSAGE);
				e.printStackTrace(); //print to console
			} finally {
				notify(null);
			}
		}

		private void notify(Object arg) {
			SWAzamRuntime.getRuntime().setChanged();
			SWAzamRuntime.getRuntime().notifyObservers(arg);
		}
	}

	private ClientService createServerStub() {
		HttpInvokerProxyFactoryBean factoryBean = new HttpInvokerProxyFactoryBean();
		factoryBean.setServiceUrl(props.getProperty("server.url"));
		factoryBean.setServiceInterface(ClientService.class);
		factoryBean.afterPropertiesSet();
		return (ClientService) factoryBean.getObject();
	}
}
